/**
 * 
 */
package white440.truck;

import java.awt.Container;
import java.awt.GridLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.image.BufferedImage;
import java.net.InetAddress;
import java.net.MalformedURLException;
import java.net.UnknownHostException;
import java.rmi.Naming;
import java.rmi.NotBoundException;
import java.rmi.RMISecurityManager;
import java.rmi.Remote;
import java.rmi.RemoteException;
import java.rmi.registry.LocateRegistry;
import java.rmi.registry.Registry;
import java.rmi.server.UnicastRemoteObject;
import java.util.Properties;
import java.util.Scanner;

import javax.swing.ImageIcon;
import javax.swing.JButton;
import javax.swing.JComboBox;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JTextField;

import white440.common.RemoteController;
import white440.common.RemoteTruck;

/**
 * @author emw5051
 *
 */
public class Rover extends UnicastRemoteObject implements RemoteTruck {



	/**
	 * 
	 */
	private static final long serialVersionUID = 3556159448329231802L;
	//webcam stuff
	private static final String[] webcamNames = {"camera1.properties",
	"camera2.properties"};
	private FrameGrabber[] webcams = null;
	//remote stuff
	private RemoteController control = null;
	private InetAddress controllerAddress = null;
	//servo stuff
	private ServoControlBoard servoBoard = null;
	private Integer throttleServoNumber = null;
	private Integer steeringServoNumber = null;

	private boolean isConfigured = false;

	private int lastThrottle = 0;
 
	//consructor
	/**Use when you don't want to bind the rmi to the ip Address InetAddress.getLocalHost().getHostAddress().
	 * This constructor is useful with vpn where the local ip addr is not the ip addr
	 *  that is exposed to the internet.
	 * Sets up the web cams using the info in "camera1.properties",
	 * "camera2.properties".
	 * @param localIPAddr the ip address to bind the rmi to. usefull with vpn
	 * @throws RemoteException
	 */
	protected Rover(String localIPAddr) throws RemoteException{
		super();
		setup(localIPAddr);
	}

	//constructor
	/**The default constructor. Makes a new rover.
	 * Binds the rmi to InetAddress.getLocalHost().getHostAddress().
	 * Sets up the web cams using the info in "camera1.properties",
	 * "camera2.properties".
	 * @throws RemoteException
	 */
	protected Rover() throws RemoteException {
		super();
		String ip = "localhost";//default to localhost
		try {
			ip = InetAddress.getLocalHost().getHostAddress();
		} catch (UnknownHostException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		setup(ip);

	}

	private void setup(String localIP) throws RemoteException{
		webcams = new FrameGrabber[webcamNames.length];
		startCapture();

		//make window to easily exit program and to set constants
		JFrame frame = new JFrame("Truck Local Maintence Terminal");
		frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
		frame.getContentPane().setLayout(new GridLayout(6,2));
		buildLocalGUI(frame.getContentPane());
		frame.pack();
		frame.setVisible(true);

		//wait until configured
		//              while(!isConfigured){
		//                      try {
		//                              wait();
		//                      } catch (InterruptedException e) {
		//                              //  Auto-generated catch block
		//                              e.printStackTrace();
		//                      }
		//              }

		//export to rmi
		String rmiAddr = "rmi://" +localIP + "/" + RemoteTruck.RMINAME;
		Registry registry = LocateRegistry.getRegistry(localIP);
		registry.rebind(RemoteTruck.RMINAME, (RemoteTruck)this);
		System.out.println("Rmi under: " + rmiAddr);
	}

	private void buildLocalGUI(Container cont) {
		//select comm port #
		cont.add(new JLabel("Select Comm Port:"));
		Integer[] commChoices = {1,2,3,4,5,6,7,8,9,10};
		final JComboBox commPortSelector = new JComboBox(commChoices);
		commPortSelector.setEditable(false);
		cont.add(commPortSelector);

		//select throttle servo #
		cont.add(new JLabel("Throtle Servo Number:"));
		final JTextField throttleServoSelector = new JTextField();
		cont.add(throttleServoSelector);

		//select seteering servo #
		cont.add(new JLabel("Steering Servo Number:"));
		final JTextField steeringServoSelector = new JTextField();
		cont.add(steeringServoSelector);

		//cameras moved to constructor
		//              //select forward webcam name
		//              cont.add(new JLabel("Forward Webcam Name"));
		//              final JTextField forwardCamSelector = new JTextField();
		//              cont.add(forwardCamSelector);
		//
		//              //select rear webcam name
		//              cont.add(new JLabel("Rear Webcam Name"));
		//              final JTextField rearCamSelector = new JTextField();
		//              cont.add(rearCamSelector);

		//button to set values
		JButton butt = new JButton("Set Values");
		cont.add(butt);
		butt.addActionListener(new ActionListener(){
			@Override
			public void actionPerformed(ActionEvent arg0) {
				if (servoBoard != null){
					servoBoard.finalize();
				}
				Scanner scan = new Scanner(commPortSelector.getSelectedItem().toString());
				try{
					servoBoard = new ServoControlBoard(scan.nextInt());
				}catch(Exception e){
					System.out.println("could not connect to servos");
					e.printStackTrace();
				}

				scan = new Scanner(throttleServoSelector.getText());
				throttleServoNumber = scan.nextInt();

				scan = new Scanner(steeringServoSelector.getText());
				steeringServoNumber = scan.nextInt();

				//                              webcamNames[0] = forwardCamSelector.getText();
				//                              webcamNames[1] = rearCamSelector.getText();     

				isConfigured = true;

			}                                       
		});

	}

	private void startCapture() {
		//              webcams = new FrameGrabber[webcamNames.length];
		for (int i = 0; i < webcams.length; i++) {
			try {
				webcams[i] = new FrameGrabber(webcamNames[i]);
				System.out.println("webcam "+i+" acivated");
			} catch (FrameGrabberException e) {
				System.out.println("Could not activate camera: "+i);
				e.printStackTrace();
			} catch (Throwable th){
				System.out.println("Could not activate camera: "+i);
				th.printStackTrace();
			}
		}
	}       

	@Override
	public ImageIcon getCameraImage(int camera) throws RemoteException {
		BufferedImage buffimg = webcams[camera].getBufferedImage();
		ImageIcon ans = new ImageIcon(buffimg);
		return ans;
	}

	/* (non-Javadoc)
	 * @see white440.common.RemoteTruck#isConnected()
	 */
	@Override
	public boolean isConnected() throws RemoteException {
		return true;
	}

	/* (non-Javadoc)
	 * @see white440.common.RemoteTruck#setController(white440.common.RemoteController)
	 */
	@Override
	public void setController(RemoteController controller) throws RemoteException {
		this.control = controller;
		try{
			this.controllerAddress = control.getIpAddress();
		}catch(RemoteException e){
			e.printStackTrace();
			lostConnection();
		}
	}

	/* (non-Javadoc)
	 * @see white440.common.RemoteTruck#setDireciton(int)
	 */
	@Override
	public boolean setDireciton(int degrees) throws RemoteException {
		return setDirectionHelper(degrees);             
	}

	/**Note: will scale degrees down to the actual range of turning for the truck 
	 * (about 30 in each direction). So in effect degrees=-90 just means turn left 
	 * as hard as you can.
	 * @param degrees to turn - is left + is right. degrees must be in [-90,90]
	 * @return true if no errors were detected in setting the servos
	 */
	private boolean setDirectionHelper(int degrees){
		boolean set = false;//was the direction set
		//check if we are still connected to our controller
		try{
			if(control.isConnected()){
				try {
					//scale 90 to 30
					int scaledDegrees = -degrees / 3;
					//check that the degrees were scaled correctly
					if(Math.abs(scaledDegrees) > 30){//error condition
						//fix by setting scaledDegrees to 30 or -30
						if(scaledDegrees > 0){
							scaledDegrees = 30;
						}else{
							scaledDegrees = -30;
						}//end if for correction
					}//end if for error conditions
					//set the direction
					servoBoard.setServoPosition(steeringServoNumber, scaledDegrees);
					set = true;//direction was set
				} catch (ServoNotSetException e) {
					set = false;//direction was not set
					e.printStackTrace();
				}
			}
		}catch(RemoteException e){
			//control is not connected
			e.printStackTrace();
			lostConnection();
		}
		return set;
	}

	/* (non-Javadoc)
	 * @see white440.common.RemoteTruck#setThrottle(int)
	 */
	@Override
	public boolean setThrottle(int percent) throws RemoteException {
		return setThrottleHelper(percent);
	}

	/**a local method to set the throttle in percent.
	 * NOTE: this method applies a governor that makes 100% throttle only
	 * about 10% of the physical throttle. This is to slow the truck down.
	 * @param percent percent throttle in [-100,100]
	 * @return true if no errors were detected in setting the throttle
	 */
	private boolean setThrottleHelper(int percent) {
		boolean set = false;//throttle was not set yet
		try{
			if(control.isConnected()){//check if control is connected
				if (lastThrottle > 0 && lastThrottle < 2 && percent < 0 && percent != 0){
					setThrottleHelper(0);
				}
				else if (lastThrottle < 0 && lastThrottle > -2 && percent > 0 && percent != 0){
					setThrottleHelper(0);
				}
				else{
					try {//set the servo position
						//max throttle is 10%
						double governedPercent = percent/5.;
						//scaling factor between throttle percent and servo angle
						int angle = (int) Math.round(governedPercent*(9./10.));
						//do a check to make sure angle is in the expected range
						if(Math.abs(angle) > 20){//this is an error condition
							//fix by replacing angle with max/min value
							if(angle > 0){
								angle = 20;
							}else{
								angle = -20;
							}//end if for asigning angle
						}//end if for checking error condition
						servoBoard.setServoPosition(throttleServoNumber,angle);
						set = true;//throttle was set
						lastThrottle = angle;
					} catch (ServoNotSetException e) {
						e.printStackTrace();
						set = false;//throttle was not set
					}
				}
			}
		}catch(RemoteException e){
			e.printStackTrace();
			lostConnection();
		}
		return set;
	}

	/**
	 * call when the connection to the controller is noticed.
	 * Stops the truck and attempts to reconnect.
	 */
	private void lostConnection(){
		setThrottleHelper(0);//stop the truck
		//try to reconnect 
		boolean connected = false;
		synchronized(this){//becasue we want to read, check, write atomically
			do{//we will release the lock when we wait
				String controlName = "rmi://"+controllerAddress+"/"+RemoteController.RMINAME;
				//try to reconnect
				try {
					Remote remote = Naming.lookup(controlName);
					if(remote instanceof RemoteController){//type check
						this.control = (RemoteController) remote;
					}
				} catch (MalformedURLException e) {
					e.printStackTrace();
				} catch (RemoteException e) {
					System.out.println("Reconnect failed: could not communicate");
				} catch (NotBoundException e) {
					System.out.println("Reconnect failed: not bound");
				}
				try {//check connection
					connected = control.isConnected();//this will set to true
				} catch (RemoteException e) { 
					connected = false;//this will set to false
				}
				if(!connected){//skip wait if we can
					try {//wait a while and try again
						wait(3000);
					} catch (InterruptedException e) {//do nothing
					}
				}
				try {//check connection again
					connected = control.isConnected();//this will set to true
				} catch (RemoteException e) { 
					connected = false;//this will set to false
				}
			}while(!connected);//continue trying until we are reconnected
		}
	}//end lostConnection

	/**
	 * @param args args[0] is the address to bind the local rmi to
	 */
	public static void main(String[] args) {
		Properties props = System.getProperties();
		System.out.println("java.library.path: "
				+ props.getProperty("java.library.path"));
		System.out.println("java.class.path: "
				+ props.getProperty("java.class.path"));
		//make sure the rmiregisry is started
		//should be done before this program is run
		//              try {
		//                      String command = "rmiregistry";
		//                      System.out.println("Starting rmiregistry with the command: " + command);
		//                      Runtime.getRuntime().exec(command);
		//              } catch (IOException e1) {
		//                      //  Auto-generated catch block
		//                      e1.printStackTrace();
		//              }

		if (System.getSecurityManager() == null) {//set security
			System.setSecurityManager(new RMISecurityManager());
		}
		//parse input
		String bindIP = null;
		if(args.length >= 1){
			bindIP = args[0];
			System.out.println("Specified bind ipaddr: " + bindIP);
		}

		try {
			//initialize the rover
			Rover rov;
			if(bindIP == null){
				rov = new Rover();
			}else{
				rov = new Rover(bindIP);
			}
			JFrame frame = new JFrame("pic");
			frame.add(new JLabel(rov.getCameraImage(0)));
			frame.pack();
			frame.setVisible(true);
		} catch (RemoteException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}

	}

}
//Hide details
//Change log
//r32 by halful01 on Yesterday (27 hours ago)   Diff
//updated readme to include escapeing and
//improved readability
//added scaling factors and error checks for
//setting the throttle and the steering.
//Go to: 	
//Double click a line to add a comment
//Older revisions
// r29 by halful01 on Mar 29, 2009   Diff 
// r28 by halful01 on Mar 29, 2009   Diff 
// r20 by halful01 on Mar 19, 2009   Diff 
//All revisions of this file
//File info
//Size: 12955 bytes, 417 lines
//View raw file
